define([], function () {
    function RequestWorker(workerUrl, bufferSize) {
        var self = this;
        this.callback = null;
        this.onprogress = null;
        this.errback = null;
        this.onFinish = null;
        this.BUFFER_SIZE = bufferSize || 10240;
        this._worker = new Worker(workerUrl || 'workers/xhr.js');

        this._receiveBuffer = null;
        this._receiveChunks = 0;
        this._maxChunks = 0;

        this._worker.addEventListener('message', function (e) {
            var resp = e.data;
            if (resp.status === 0) {
                // if arraybuffer mode, ignore this, wait for buffer callbacks
                self.callback && self.callback(resp.message);
                self.onFinish && self.onFinish();
            } else if (resp.status === -1) {
                self.errback && self.errback(resp.message);
                self.onFinish && self.onFinish();
            } else if (resp.status === 1) {
                self.onprogress && self.onprogress(resp.message);
            } else if (resp.status === 3) {
                // request more buffers to return data from workers
                var newBuffer = new ArrayBuffer(resp.message.bufferSize);
                self._buffer = resp.message.buffer;
                self._worker.postMessage({
                    command: 'buffer',
                    opts: {
                        buffer: newBuffer,
                        bufferSize: resp.message.bufferSize,
                    },
                }, [newBuffer]);
            } else if (resp.status === 2) {
                // on buffer, collect buffer in arraybuffer mode
                var buffer = resp.buffer;
                var byteStart = resp.byteStart;
                var byteCount = resp.byteCount;
                var totalLength = resp.totalLength;

                self._maxChunks = resp.maxChunks;

                if (!self._receiveBuffer) {
                    self._receiveBuffer = new Uint8Array(totalLength);
                }
                for (var i = 0; i < byteCount; i++) {
                    var writeTo = byteStart + i;
                    self._receiveBuffer[writeTo] = buffer[i];
                }
                self._receiveChunks++;

                if (self._receiveChunks === self._maxChunks && self._maxChunks) {
                    self.callback && self.callback(self._receiveBuffer.buffer);
                    self.onFinish && self.onFinish();
                    self._receiveBuffer = null;
                    self._receiveChunks = 0;
                    self._maxChunks = 0;
                }
            }
        });
        this._getBuffer = function () {
            if (!self._buffer) {
                self._buffer = new ArrayBuffer(self.BUFFER_SIZE);
            }
            return self._buffer;
        };
        this._executeReq = function (req, callback, errback, onprogress, onFinish) {

            self._receiveBuffer = null;
            self._receiveChunks = 0;
            self._maxChunks = 0;

            self.callback = callback;
            self.errback = errback;
            self.onprogress = onprogress;
            self.onFinish = onFinish;

            self._worker.postMessage({
                command: 'abort',
            });
            var url = req._url;
            var opts = req._opts;

            var callOpts = copyPrimitive(opts);
            var transferables = undefined;
            if (opts.responseType === 'arraybuffer') {
                callOpts.buffer = self._getBuffer();
                callOpts.bufferSize = self.BUFFER_SIZE;
                transferables = [callOpts.buffer];
            }

            self._worker.postMessage({
                url: url,
                opts: callOpts,
            }, transferables);
        };
        this._abortReq = function () {
            self._worker.postMessage({
                command: 'abort',
            });
        };
        this.terminate = function () {
            self._worker.terminate();
        };
    }

    function copyPrimitive(obj) {
        var copy = {};
        Object.keys(obj).forEach(function (key) {
            var val = obj[key];
            if (val instanceof Function) {
                return;
            }
            copy[key] = obj[key];
        });
        return copy;
    }

    var REFRESH_TIME = 50; //50 ms

    function RequestPool(opts) {
        opts = opts || {};

        var self = this;

        this._debug = false;
        this._log = function () {
            if (self._debug) {
                console.log.apply(console, arguments);
            }
        };

        this.MAX_CONCURRENT = opts.maxConcurrent || navigator.hardwareConcurrency || 10;
        this.keepTimer = false;

        this._useWorkers = opts.useWorkers;
        this._availableWorkers = [];
        this._acquireWorker = function () {
            var worker = self._availableWorkers.shift();
            if (!worker) {
                worker = new RequestWorker(opts.workerUrl, opts.bufferSize);
            }
            return worker;
        };
        this._returnWorkerToPool = function (worker) {
            if (!worker || !(worker instanceof RequestWorker)) return;
            if (self._availableWorkers.length >= self.MAX_CONCURRENT) {
                worker.terminate();
                return;
            }
            self._availableWorkers.push(worker);
        };

        this._todo = [];
        this._doing = [];
        this._done = [];

        var timer = null;

        this._start = function () {
            if (!timer) {
                timer = setInterval(self.refresh.bind(self), REFRESH_TIME);
            }
        };

        this._pause = this._stop = function () {
            if (timer) {
                clearInterval(timer);
                timer = null;
            }
        };

        this.sum = 0;
        this.succ = 0;
        this.fail = 0;

        this.resetCounter = function () {
            self.sum = self.sum - self.succ - self.fail;
            self.succ = 0;
            self.fail = 0;
        };

    }

    var MAX_IDLE = 50000; //50s max idle, or kill timer

    RequestPool.prototype = {
        add: function (request) {
            if (!request) return;
            this._todo.push(request);
            this._start();
            this.sum++;
        },
        refresh: function () {

            var self = this;

            var todo = [].concat(this._todo);
            var oldDoing = [].concat(this._doing);
            // var oldDone = [].concat(this._done);

            //we throw away done tasks here

            var newDoing = [];
            // var newDone = [];

            oldDoing.forEach(function (req) {
                if (req._stateFin || req._stateError) {
                    // newDone.push(task);
                    if (req._stateFin) {
                        self.succ++;
                        self._log('task fin ' + req._id + ": " + self.succ + "/" + self.fail + "/" + self.sum);
                    } else {
                        self.fail++;
                        self._log('task err ' + req._id + ": " + self.succ + "/" + self.fail + "/" + self.sum);
                    }
                } else if (req._stateOnGoing) {
                    newDoing.push(req);
                } else {
                    newDoing.push(req);
                    // newTodo.push(task);
                }
            });

            if (newDoing.length < this.MAX_CONCURRENT) {
                var seats = this.MAX_CONCURRENT - newDoing.length;
                seats = Math.min(todo.length, seats);
                if (seats > 0) {
                    var promoted = todo.splice(0, seats);
                    promoted.forEach(function (req) {
                        if (!req) return;
                        // self._log('task start ' + req._id);
                        newDoing.push(req);
                        var worker = null;
                        if (self._useWorkers) {
                            worker = self._acquireWorker();
                        }
                        req._execute(function returnWorker() {
                            self._returnWorkerToPool(worker);
                        }, worker);
                    });
                }
            }

            this._todo = todo;
            this._doing = newDoing;
            // this._done = [];

            if ((todo.length + newDoing.length) === 0 && !this.keepTimer) {
                var now = Date.now();
                if (this._lastIdle) {
                    if (now - this._lastIdle > MAX_IDLE) {
                        this._stop();
                    }
                }
                this._lastIdle = now;
            }
        }

    };

    return RequestPool;
});